package com.zhtools.utils;

import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class HttpClientUtils {
    private static final int READTIMEOUTMS = 15000;
    private static final int CONNECTTIMEOUTMS = 60000;
    private static final String CHARSET = "UTF-8";
    private static final int STATUSCODE_OK = 200;

    /**
     * 创建HttpClient实例
     *
     * @return
     */
    private static CloseableHttpClient createHttpClient() {
        BasicHttpClientConnectionManager connManager = new BasicHttpClientConnectionManager(
                RegistryBuilder.<ConnectionSocketFactory>create()
                        .register("http", PlainConnectionSocketFactory.getSocketFactory())
                        .register("https", SSLConnectionSocketFactory.getSocketFactory())
                        .build(), null, null, null
        );
        RequestConfig config = RequestConfig.custom()
                .setConnectTimeout(CONNECTTIMEOUTMS)
                .setSocketTimeout(READTIMEOUTMS).build();
        CloseableHttpClient httpClient = HttpClientBuilder.create()
                .setConnectionManager(connManager)
                .setDefaultRequestConfig(config)
                .build();
        return httpClient;
    }

    /**
     * 创建HttpClient实例 双向证书
     *
     * @param pwd  证书密码
     * @param path 证书文件路径
     * @return
     */
    private static CloseableHttpClient createHttpClientForSSL(String pwd, String path) throws Exception {
        // 参数验证
        if (StringUtils.isEmpty(pwd)) {
            throw new IllegalArgumentException("证书密码不能为空!");
        }
        if (StringUtils.isEmpty(path)) {
            throw new IllegalArgumentException("证书路径不能为空!");
        }
        File file = new File(path);
        if (!file.isFile()) {
            throw new IllegalArgumentException("证书路径不是一个文件!");
        }
        if (!file.exists()) {
            throw new IllegalArgumentException("证书文件路径不存在!");
        }

        // 证书
        char[] password = pwd.toCharArray();
        InputStream certStream = new FileInputStream(file);
        KeyStore ks = KeyStore.getInstance("PKCS12");
        ks.load(certStream, password);

        // 实例化密钥库 & 初始化密钥工厂
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(ks, password);

        // 创建 SSLContext
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());

        SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
                sslContext,
                new String[]{"TLSv1"},
                null,
                new DefaultHostnameVerifier());

        BasicHttpClientConnectionManager connManager = new BasicHttpClientConnectionManager(
                RegistryBuilder.<ConnectionSocketFactory>create()
                        .register("http", PlainConnectionSocketFactory.getSocketFactory())
                        .register("https", sslConnectionSocketFactory)
                        .build(),
                null,
                null,
                null
        );
        RequestConfig config = RequestConfig.custom()
                .setConnectTimeout(CONNECTTIMEOUTMS)
                .setSocketTimeout(READTIMEOUTMS).build();
        CloseableHttpClient httpClient = HttpClientBuilder.create()
                .setConnectionManager(connManager)
                .setDefaultRequestConfig(config)
                .build();
        return httpClient;
    }

    private static List<NameValuePair> getNameValuePairs(Map<String, String> params) {
        List<NameValuePair> pairs = new ArrayList<>(params.size());
        for (Map.Entry<String, String> entry : params.entrySet()) {
            String value = entry.getValue();
            if (value != null) {
                pairs.add(new BasicNameValuePair(entry.getKey(), value));
            }
        }
        return pairs;
    }

    public static String doGet(String url) throws Exception {
        return doGet(false, url, null, CHARSET, null, null);
    }

    public static String doGetWithSSL(String url, String pwd, String path) throws Exception {
        return doGet(false, url, null, CHARSET, pwd, path);
    }

    public static String doGet(String url, Map<String, String> params) throws Exception {
        return doGet(false, url, params, CHARSET, null, null);
    }

    public static String doGetWithSSL(String url, Map<String, String> params, String pwd, String path) throws Exception {
        return doGet(true, url, params, CHARSET, pwd, path);
    }

    public static String doGet(boolean ssl, String url, Map<String, String> params, String charset, String pwd, String path) throws Exception {
        if (StringUtils.isBlank(url)) {
            return null;
        }
        if (params != null && !params.isEmpty()) {
            List<NameValuePair> pairs = getNameValuePairs(params);
            url += "?" + EntityUtils.toString(new UrlEncodedFormEntity(pairs, charset));
        }

        String result = null;
        CloseableHttpClient httpClient = ssl ? createHttpClientForSSL(pwd, path) : createHttpClient();
        try {
            HttpGet httpGet = new HttpGet(url);
            CloseableHttpResponse response = httpClient.execute(httpGet);
            try {
                int statusCode = response.getStatusLine().getStatusCode();
                if (statusCode != STATUSCODE_OK) {
                    httpGet.abort();
                    throw new RuntimeException("HttpClient,error status code :" + statusCode);
                }
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    result = EntityUtils.toString(entity, charset);
                }
                EntityUtils.consume(entity);
            } finally {
                if (response != null) {
                    response.close();
                }
            }
        } finally {
            if (httpClient != null) {
                httpClient.close();
            }
        }
        return result;
    }


    public static String doPost(String url, Map<String, String> params) throws Exception {
        return doPost(false, url, null, params, null, CHARSET, null, null);
    }

    public static String doPostWithSSL(String url, Map<String, String> params, String pwd, String path) throws Exception {
        return doPost(true, url, null, params, null, CHARSET, null, null);
    }

    public static String doPost(String url, String data, Map<String, String> header) throws Exception {
        return doPost(false, url, data, null, header, CHARSET, null, null);
    }

    public static String doPostWithSSL(String url, String data, Map<String, String> header, String pwd, String path) throws Exception {
        return doPost(true, url, data, null, header, CHARSET, pwd, path);
    }

    public static String doPost(
            boolean ssl, String url, String data,
            Map<String, String> params, Map<String, String> header, String charset,
            String pwd, String path) throws Exception {
        if (StringUtils.isBlank(url)) {
            return null;
        }
        String result = null;
        CloseableHttpClient httpClient = ssl ? createHttpClientForSSL(pwd, path) : createHttpClient();
        try {
            HttpPost httpPost = new HttpPost(url);
            if (header != null && header.size() > 0) {
                for (Map.Entry<String, String> entry : header.entrySet()) {
                    httpPost.addHeader(entry.getKey(), entry.getValue());
                }
            }
            if (StringUtils.isNotEmpty(data)) {
                StringEntity postEntity = new StringEntity(data, charset);
                httpPost.setEntity(postEntity);
            }
            if (params != null && params.size() > 0) {
                List<NameValuePair> pairs = getNameValuePairs(params);
                httpPost.setEntity(new UrlEncodedFormEntity(pairs, CHARSET));
            }
            CloseableHttpResponse response = httpClient.execute(httpPost);
            try {
                int statusCode = response.getStatusLine().getStatusCode();
                if (statusCode != STATUSCODE_OK) {
                    httpPost.abort();
                    throw new RuntimeException("HttpClient,error status code :" + statusCode);
                }
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    result = EntityUtils.toString(entity, charset);
                }
                EntityUtils.consume(entity);
            } finally {
                if (response != null) {
                    response.close();
                }
            }
        } finally {
            if (httpClient != null) {
                httpClient.close();
            }
        }
        return result;
    }
}
